using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;


namespace Rogue
{
    // The two windows (that with 'E' or 'W') shows your selected enemy or yourself

    public class DetailWindow : TextWindow
    {
        public static DetailWindow HeroWindow = null;
        public static DetailWindow EnemyWindow = null;

        Texture2D m_LargeTexture;
        Entity m_CurrEntity;
        BodyPart m_BodyPartToDamage;

        bool m_bShouldHideAfterTime = false;
        float m_TimeSinceAutoShow = 0.0f;
        int m_TimeOfAutoShow;       // timeline time

        ButtonState m_PrevLMB;

        struct DeferredRectDraw
        {
            public DeferredRectDraw(Texture2D a_tex, Rectangle a_rect, Color a_col)
            {
                tex = a_tex;
                rect = a_rect;
                col = a_col;
            }

            public Texture2D   tex;
            public Rectangle rect;
            public Color col;
        };

        List<DeferredRectDraw> m_BodyDrawing;
        Rectangle m_BodyRect;

        public DetailWindow(
            Texture2D a_Tex,
            Texture2D a_LargeTexture,
            GraphicsDevice a_Dev,
            Point a_pPos,
            Point a_pCellMapSize,
            Point a_pCellScreenSize,
            Point a_pCharacterOffset,
            int a_nTextBufferSize
            )
            : base(a_Tex, a_Dev, a_pPos, a_pCellMapSize, a_pCellScreenSize, a_pCharacterOffset)
        {
            InputManager.Instance.OnKeyPressed += new InputManager.OnKeyPressedHandler(Input_OnKeyPressed);

            m_LargeTexture = a_LargeTexture;

            m_BodyDrawing = new List<DeferredRectDraw>();

            DrawBackground = true;
            BackgroundColor = Color.Black;

            BorderBottom = BorderTop = BorderLeft = BorderRight = true;
            BorderColor = Color.Gold;
        }

        public bool IsActive
        { get { return m_CurrEntity != null; } }

        public Entity FocusEntity
        { get { return m_CurrEntity; } }

        public BodyPart BodyPartToDamage
        { get { return m_BodyPartToDamage; } }

        void Input_OnKeyPressed(object a_Sender, InputManager.KeyEventArgs a_KeyEventArgs)
        {
            if (ConsoleWindow.Instance.IsActive)
                return;

            if (this == HeroWindow)
            {
                switch (a_KeyEventArgs.Key)
                {
                    case Keys.W:
                        HeroWindow.m_BodyPartToDamage = null;

                        if (HeroWindow.m_CurrEntity != null)
                        {
                            HeroWindow.m_CurrEntity = null;
                        }
                        else
                        {
                            HeroWindow.Focus(RogueGame.Instance.Map.GetHero(), false);
                        }
                        break;
                }
            }
            
            if (this == EnemyWindow)
            {
                switch (a_KeyEventArgs.Key)
                {
                    case Keys.E:
                        EnemyWindow.m_BodyPartToDamage = null;

                        if (EnemyWindow.m_CurrEntity == null || !EnemyWindow.m_CurrEntity.IsAlive)
                        {
                            Entity hero = RogueGame.Instance.Map.GetHero();
                            EnemyWindow.m_CurrEntity = null;
                            foreach (Entity e in Entity.Collector.GetAllWithinSorted<Entity>(hero.Position, 15.0f))
                            {
                                if (e != hero && IsEntityVisible(e) && e.IsAlive)
                                {
                                    EnemyWindow.Focus(e, false);
                                    break;
                                }
                            }
                        }
                        else
                        {
                            EnemyWindow.m_CurrEntity = null;
                        }

                        break;
                }
            }
        }

        public static void FocusBoth(Entity ent, bool a_Auto)
        {
            if (ent != null && ent.IsPlayerControlled)
            {
                HeroWindow.Focus(ent, a_Auto);
            }
            else
            {
                EnemyWindow.Focus(ent, a_Auto);
            }
        }

        public void Focus(Entity ent, bool shouldHide)
        {
            // if we're already showing for the same entity (and not autohiding), 
            // don't set to autohide.
            if (m_bShouldHideAfterTime || m_CurrEntity != ent)
            {
                m_bShouldHideAfterTime = shouldHide;
                if (shouldHide)
                    m_TimeSinceAutoShow = 0.0f;
            }

            m_TimeOfAutoShow = -1;      // kind of goofy :/ On the next player turn remember the time
            m_CurrEntity = ent;
            m_BodyPartToDamage = null;
        }

        // TODO: entity cycling

        static bool IsEntityVisible(Entity a_Ent)
        {
            if (a_Ent == null)
                return false;

            if (RogueGame.Instance.Map.m_AllVisible)
                return true;

            LightCaster.RayData ray = RogueGame.Instance.Map.LightCaster.GetRay(a_Ent.Position);
            return ray != null && ray.m_IsVisible;
        }

        public static void UpdateInput()
        {
            MouseState mouse = Mouse.GetState();
            if (mouse.LeftButton == ButtonState.Pressed)
            {
                Entity ent = RogueGame.Instance.Map.GetEntityAt(RogueGame.Instance.Map.Camera.MapMouseHoverCell);
                bool isHero = ent != null && ent.IsPlayerControlled;
                
                if (IsEntityVisible(ent))
                {
                    FocusBoth(ent, false);
                }
            }

            if (EnemyWindow.WindowBounds.Contains(new Point(mouse.X, mouse.Y)))
            {
                EnemyWindow.m_bShouldHideAfterTime = false;
            }
        }

        Rectangle GetDetailBBox(DetailWindowImages images, string partName)
        {
            Rectangle bbox = images.GetPartBBox(partName);
            Point texSize = images.GetTexSize(partName);

            float scale = (float)m_BodyRect.Width / texSize.X;
            bbox.Width = (int)(bbox.Width * scale);
            bbox.Height = (int)(bbox.Height * scale);
            bbox.X = (int)(m_BodyRect.X + bbox.X * scale);
            bbox.Y = (int)(m_BodyRect.Y + bbox.Y * scale);

            return bbox;
        }

        public override void Update(GameTime dt)
        {
            m_TimeSinceAutoShow += (float) dt.ElapsedGameTime.TotalSeconds;

            if (m_CurrEntity != null)
            {
                Entity hero = RogueGame.Instance.Map.GetHero();
                if (Timeline.Instance.CurrentEntity == hero && m_TimeOfAutoShow == -1)
                    m_TimeOfAutoShow = Timeline.Instance.CurrentEntity.NextThink;

                bool interestingHealth = m_CurrEntity.IsAlive && m_CurrEntity.IsCriticallyWounded();
                if (Timeline.Instance.CurrentTime > m_TimeOfAutoShow && m_bShouldHideAfterTime && m_TimeSinceAutoShow > 5.0f && !interestingHealth)
                {
                    m_CurrEntity = null;
                }

                if (!IsEntityVisible(m_CurrEntity))
                    m_CurrEntity = null;
            }
        }

        public override void Draw(GraphicsDevice a_Device)
        {
            if (!IsActive)
            { 
                ClearBuffer();
                DrawBorder();
                return;
            }

            base.Draw(a_Device);



            if (m_CurrEntity == null)
                return;

            string title = m_CurrEntity.Name;

            EntityDef EntDef = EntityDatabase.Instance.GetDefinition(m_CurrEntity.GetType().Name);

            int titleWidth = title.Length * 16;
            Point titleStart = new Point(Position.X + (WindowWidth - titleWidth) / 2, Position.Y + 4);
            PutFloatingString(title, titleStart, new Color(EntDef.DisplayColor), m_LargeTexture, new Point(16, 16), Point.Zero);

            if (m_CurrEntity != null)
            {
                string subTitle = m_CurrEntity.GetType().ToString();
                if (this == HeroWindow)
                    subTitle = "(this is you!)";

                int subtitleWidth = subTitle.Length * (m_pCellScreenSize.X + m_pCharacterOffset.X);
                Point subtitleStart = new Point(Position.X + (WindowWidth - subtitleWidth) / 2, Position.Y + 20);
                PutFloatingString(subTitle, subtitleStart, new Color(EntDef.DisplayColor));
            }


            string toClose = "('W' to open or close this window)";
            if (this == DetailWindow.EnemyWindow)
            {
                toClose = "('E' to open or close this window)";
            }

            titleWidth = toClose.Length * (m_pCellScreenSize.X + m_pCharacterOffset.X);
            PutString(toClose, (Width - toClose.Length) / 2, Height - 1, Color.Gray);

            if (m_CurrEntity == null)
            {
                return;
            }

            if (!IsEntityVisible(m_CurrEntity))
            {
                PutString("<out of sight>", 2, 2, Color.LightGray);
                return;
            }

            // show which entity it is
            if (this == DetailWindow.EnemyWindow)
                TerrainWindow.Instance.DrawCellOutline(RogueGame.Instance.Map.Camera.CellToWindowSpace(m_CurrEntity.Position), Color.PaleGoldenrod);

            DetailWindowImages images = m_CurrEntity.GetDetailWindowImages();

            Texture2D background = images.GetImageFor("background");

            float imageRatio = (float)background.Height / background.Width;
            m_BodyRect = new Rectangle(Position.X + 1, Position.Y + 32, WindowWidth - 2, (int)((WindowWidth - 2) * imageRatio));

            // the rest is showing bodypart details...
            Color totalHealthColor = Color.WhiteSmoke;
            if (m_CurrEntity.BodyRoot != null)
            {
                float totalHealthFrac = (float)m_CurrEntity.BodyRoot.GetTotalHealth() / m_CurrEntity.BodyRoot.GetTotalMaxHealth(true);
                if (!m_CurrEntity.IsAlive)
                    totalHealthFrac = 0.0f;
                totalHealthColor = BodyPart.GetDefaultColorFromHealth(totalHealthFrac);
            }

            m_BodyDrawing.Add(new DeferredRectDraw(background, m_BodyRect, new Color(totalHealthColor.ToVector3() * 0.3f)));

            bool isDefault = (images.TypeName == "default");
            if (isDefault)
            {
                Texture2D questionMark = images.GetImageFor("default");
                m_BodyDrawing.Add(new DeferredRectDraw(questionMark, m_BodyRect, totalHealthColor));
            }

            if (m_CurrEntity.BodyRoot == null)
                return;

            int x = 1;
            int y = m_BodyRect.Bottom / CellScreenSize.Y;

            if (m_CurrEntity.Speed != -1)
            {
                if (m_CurrEntity.IsAlive)
                {
                    string speed = m_CurrEntity.GetStatusDescription();
                    if (speed != null)
                    {
                        PutString(speed, x, y);
                        ++y;
                    }

                    if (this == EnemyWindow)
                    {
                        string attacks = "attacks: ";
                        foreach (Attack attack in m_CurrEntity.GetAttacks(true))
                        {
                            if (attack.m_Conferrer != "")
                            {
                                attacks += String.Format("{0} (with {1})", attack.m_YouVerb, attack.m_Conferrer);
                            }
                            else
                            {
                                attacks += String.Format("{0}", attack.m_YouVerb);
                            }
                        }
                        PutString(attacks, x, y);
                        y += 2;
                    }
                }
                else
                {
                    PutString("dead", x, y);
                    ++y;
                }
            }

            MouseState mouse = Mouse.GetState();
            Point clickPos = new Point(mouse.X, mouse.Y);
            BodyPart highlightPart = null;

            Entity hero = RogueGame.Instance.Map.GetHero();
            if (m_CurrEntity != null && m_CurrEntity.IsAlive && (m_PrevLMB == ButtonState.Released || !hero.CanAttack(m_CurrEntity)))
            {
                foreach (BodyPart p in m_CurrEntity.BodyRoot.GetAllParts<BodyPart>())
                {
                    // TODO: test pixels instead of bboxes
                    if (GetDetailBBox(images, p.GetLocationAndName()).Contains(clickPos))
                    {
                        highlightPart = p;
                    }
                }
            }

            bool canAttackAny = false;

            foreach (BodyPart p in m_CurrEntity.BodyRoot.GetAllParts<BodyPart>())
            {
                Color partColor = p.GetColorFromHealth();
                if (p == highlightPart)
                    partColor = Color.CornflowerBlue;

                String descr = string.Format("{0} - {1}", p.GetLocationAndName(), p.DescribeCurrHealth());
                if (this == EnemyWindow && hero.CanAttack(m_CurrEntity))
                {
                    canAttackAny = true;
                    float chanceToHit = m_CurrEntity.GetChanceToHit(p) * 100.0f;

                    PutString(string.Format("{0}", p.m_AttackIndex), x - 1, y, Color.LightSlateGray);
                }

                PutString(descr, x + 1, y, partColor);

                ++y;

                if (!p.m_DrawWhenDead && !p.Owner.IsAlive)
                    continue;

                if (!isDefault)
                {
                    string partName = p.GetLocationAndName();
                    Texture2D partTex = images.GetImageFor(partName);
                    m_BodyDrawing.Add(new DeferredRectDraw(partTex, m_BodyRect, partColor));
                }
            }

            // attack selection

            if (this == HeroWindow && m_CurrEntity.Items.Count > 0)
            {
                ++y;

                PutString("Current Attack:", x, y, Color.LightGreen);
                ++y;

                int attackIndex = 1;
                foreach (Attack a in m_CurrEntity.GetAttacks(false))
                {
                    PutString(string.Format("F{0}", attackIndex), x, y, Color.LightGreen);
                    ++attackIndex;

                    Color attackColor = Color.LightSlateGray;
                    if (m_CurrEntity.GetSelectedAttack() == a)
                        attackColor = Color.LightGreen;

                    PutString(string.Format("{0}{1}{2}: {3} damage", a.m_YouVerb, (a.m_Conferrer != "" ? " with " : ""), a.m_Conferrer, a.m_Type), x + 3, y, attackColor);
                    ++y;
                }

                PutString("(select an attack with the F keys)", x - 1, y, Color.LightGreen);
                ++y;
            }



            if (canAttackAny)
            {
                string toAttack = "(click to attack a part, or type a number)";
                PutString(toAttack, (Width - toAttack.Length) / 2, y, Color.LightSlateGray);
                y++;
            }
            else if (m_BodyRect.Contains(clickPos) && this != HeroWindow)
            {
                // PutFloatingString("(can't attack)", new Point(mouse.X - 50, mouse.Y - 15), Color.Red);
            }

            Keys[] pressedKeys = InputManager.Instance.GetNewKeys();

            if (this == EnemyWindow && hero.CanAttack(m_CurrEntity))
            {
                BodyPart partToAttack = null;
                if (mouse.LeftButton == ButtonState.Pressed)
                    partToAttack = highlightPart;

                if (pressedKeys != null)
                {
                    Keys[] pickers = { Keys.D0, Keys.D1, Keys.D2, Keys.D3, Keys.D4, Keys.D5, Keys.D6, Keys.D7, Keys.D8, Keys.D9 };

                    for (int i = 0; i < pressedKeys.Length; ++i)
                    {
                        foreach (BodyPart p in m_CurrEntity.BodyRoot.GetAllParts<BodyPart>())
                        {
                            if (pressedKeys[i] == pickers[p.m_AttackIndex])
                                partToAttack = p;
                        }
                    }
                }

                if (Timeline.Instance.CurrentEntity == hero && partToAttack != null)
                {
                    m_BodyPartToDamage = highlightPart;
                    hero.MeleeAttack(m_CurrEntity, partToAttack);
                }
            }
            m_PrevLMB = mouse.LeftButton;

            ++y;

            foreach (BodyPart p in m_CurrEntity.BodyRoot.GetAllLostParts<BodyPart>())
            {
                String descr = string.Format("{0}: missing", p.GetLocationAndName(), p.DescribeCurrHealth());
                PutString(descr, x, y, Color.Maroon);
                ++y;
            }

            // next, draw all the deferred bodyparts
            m_SpriteBatch.Begin(SpriteBlendMode.AlphaBlend, SpriteSortMode.Immediate, SaveStateMode.None);
            m_SpriteBatch.GraphicsDevice.SamplerStates[0].MagFilter = TextureFilter.Point;
            foreach (DeferredRectDraw b in m_BodyDrawing)
            {
                m_SpriteBatch.Draw(b.tex, b.rect, b.col);
            }
            m_BodyDrawing.Clear();
            m_SpriteBatch.End();
        }

        public override void RenderFloatingStrings()
        {
            for (int i = 0; i < m_nFloatingStringCount; i++)
            {
                for (int j = 0; j < m_FloatingStrings[i].s.Length; j++)
                {
                    int nOffsetX = j * (m_FloatingStrings[i].CharSize.X + m_FloatingStrings[i].CharOffset.X);
                    int c = (int) m_FloatingStrings[i].s[j];

                    int x = (c << 4) & 0xff;
                    int y = c & ~0xf;
                    Rectangle cellDefRect = new Rectangle(x, y, m_FloatingStrings[i].CharSize.X, m_FloatingStrings[i].CharSize.Y);
                    
                    m_SpriteBatch.Draw(
                        m_FloatingStrings[i].Tex,
                        new Vector2(m_FloatingStrings[i].Position.X + nOffsetX, m_FloatingStrings[i].Position.Y),
                        cellDefRect,
                        new Color(m_FloatingStrings[i].Color)
                        );
                }
            }
            m_nFloatingStringCount = 0;
        }

        public override void RenderCells()
        {
            if (!IsActive)
                return;

            base.RenderCells();
        }
    }
}